package diff.viewer;

import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JEditorPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.text.JTextComponent;
import diff.Difference;

/**
 * @author WL
 */
public class DiffEditor extends JEditorPane implements PropertyChangeListener, DocumentListener
{
    private Difference[] diff;
    private boolean isLeft; //indicates that this is the editor on the left
    private boolean lineWrap;
    private int longestLineWidth;
    
    public DiffEditor()
    {
        setOpaque(false);
        addPropertyChangeListener(this);
        this.getDocument().addDocumentListener(this);
    }
    
    public void setLineWrapping(boolean val)
    {
        lineWrap = val;
    }
    
    public boolean isLeft()
    {
        return isLeft;
    }
    
    public void setLeft(boolean val)
    {
        isLeft = val;
    }
    
    public void setDifference(Difference[] diff)
    {
        this.diff = diff;
    }
    
   // Override getScrollableTracksViewportWidth
   // to preserve the full width of the text
    /**
     * Obtained from Core Advanced Swing Programming by Kim Topley
     * under heading "Text Components, Text Wrapping, and Scrolling"
     * @return 
     */
    @Override
    public boolean getScrollableTracksViewportWidth() {
        Component parent = getParent();
        ComponentUI ui = getUI();
        return parent != null ?
            (ui.getPreferredSize(this).width
            <= parent.getSize().width):
            true;
    }
    
    @Override
    public void repaint(long tm, int x, int y, int width, int height) 
    {
        // This forces repaints to repaint the entire TextPane.
        super.repaint(tm, 0, 0, getWidth(), getHeight());
    }
    
    @Override
    protected void paintComponent(Graphics g)
    {
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        
        paintDiff(g, diff, this);
        
        super.paintComponent(g);
    }
    
    private void paintDiff(Graphics g, Difference[] diff, JTextComponent comp)
    {
        Color oldColor = g.getColor();

        if (diff == null || diff.length == 0 || comp == null)
            return;

        FontMetrics fontMetrics = getFontMetrics(getFont());
        int fontHeight = fontMetrics.getHeight();

        for (int i = 0; i < diff.length; i++)
        {
            Difference curr = diff[i];
            int startLine = 0;
            int endLine = 0;
            boolean drawLine = false;
            switch (curr.getType())
            {
                case Difference.ADD:
                    drawLine = isLeft;
                    startLine = isLeft ? curr.getFirstStart() + 1 : curr.getSecondStart();
                    endLine = isLeft ? curr.getFirstStart() + 1 : curr.getSecondEnd();
                    break;
                    
                case Difference.CHANGE:
                    drawLine = false;
                    startLine = isLeft ? curr.getFirstStart() : curr.getSecondStart();
                    endLine = isLeft ? curr.getFirstEnd() : curr.getSecondEnd();
                    break;
                    
                case Difference.DELETE:
                    drawLine = !isLeft;
                    startLine = isLeft ? curr.getFirstStart() : curr.getSecondStart() + 1;
                    endLine = isLeft ?  curr.getFirstEnd() : curr.getSecondStart() + 1;
                    break;
            }
            
            int y = DiffPaneManager.getY(startLine - 1, this);
            int y1 = DiffPaneManager.getY(endLine, this);

            //renders the coloured rectangle
            if (!drawLine)
            {
                g.setColor(DiffColor.getColor(curr));
                g.fillRect(0, y, getWidth(), y1-y);
                
                g.setColor(DiffColor.colorBorder);
            
                //draws the horizontal line on top
                g.drawLine(0, y, getWidth(), y);

                //draws the horizontal line below
                g.drawLine(0, y1, getWidth(), y1);
            }
            else
            {
                g.setColor(DiffColor.colorBorder);
                g.drawLine(0, y, getWidth(), y);
            }
        }
        g.setColor(oldColor);
    }
    
    //line wrapping implementation methods
    @Override
    public Dimension getPreferredSize()
    {
        int height = super.getPreferredSize().height;
        return (lineWrap || longestLineWidth <= super.getMinimumSize().width) ? 
                super.getPreferredSize() :  new Dimension(longestLineWidth, height);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) 
    {
        if ("document".equals(evt.getPropertyName()))
            this.getDocument().addDocumentListener(this);
    }

    @Override
    public void insertUpdate(DocumentEvent e) 
    {
        updateLongest();
    }

    @Override
    public void removeUpdate(DocumentEvent e) 
    {
        updateLongest();
    }

    @Override
    public void changedUpdate(DocumentEvent e) 
    {
        updateLongest();
    }
    
    private void updateLongest()
    {
        longestLineWidth = getWidestLine();
    }
    
    private int getWidestLine()
    {
        String[] lines = this.getText().split("\n");
        int longest = 0;
        FontMetrics metrics = DiffEditor.this.getFontMetrics(DiffEditor.this.getFont());
        for (String line : lines)
        {
            char[] currArr = line.toCharArray();
            longest = Math.max(longest, metrics.charsWidth(currArr, 0, currArr.length));
        }
        return longest;
    }
}
